package com.hs.libs.api;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.hs.framework.rest.RestAPIBase;
import com.hs.libs.DeviceUnavailableException;
import com.hs.utils.Logger;
import com.hs.utils.Tools;

import java.io.File;
import java.io.IOException;
import java.util.*;

public class HeadspinAPI extends RestAPIBase {
    String baseURL;
    String token;
    HashMap<String, String> header = new HashMap<>();
    JsonObject config;
    int waitDeviceAvailableTimeout = 90; // seconds
    int polling = 5; // seconds

    public HeadspinAPI() {
        //配置导入
        String hsConfigFullPath = System.getenv("hs_config");
        config = Tools.readJsonFile(hsConfigFullPath);
        baseURL = Tools.deepGetJsonStringVal(config, new String[]{"headspin", "base_url"}
                , "default_url");
        token = Tools.deepGetJsonStringVal(config, new String[]{"headspin", "api_key"}
                , "default_token");
        header.put("Authorization", "Bearer " + token);
    }

    public JsonElement getDeviceLocalConfigByUdid(String udid) throws NullPointerException {
        JsonArray mobileDevices = Tools.deepGetJsonArrayVal(config, new String[]{"mobileapp", "headspin", "devices"});
        for (JsonElement mobileDevice : mobileDevices) {
            if (mobileDevice.getAsJsonObject().get("udid").getAsString().equalsIgnoreCase(udid)) {
                return mobileDevice;
            }
        }
        throw new NullPointerException("Can not find out the device: " + udid + " in local config.");
    }

    public JsonElement getDeviceLocalConfigByName(String deviceName) throws NullPointerException {
        JsonArray mobileDevices = Tools.deepGetJsonArrayVal(config, new String[]{"mobileapp", "headspin", "devices"});
        for (JsonElement mobileDevice : mobileDevices) {
            if (mobileDevice.getAsJsonObject().get("deviceName").getAsString().equalsIgnoreCase(deviceName)) {
                return mobileDevice;
            }
        }
        throw new NullPointerException("Can not find out the device: " + deviceName + " in local config.");
    }

    public JsonArray getLocalDevicesArrayByType(String deviceType) throws NullPointerException {
        if (!deviceType.equalsIgnoreCase("ios") && !deviceType.equalsIgnoreCase("android")) {
            throw new NullPointerException("The actual device type is " + deviceType + ", which must be android or ios");
        }
        JsonArray mobileDevices = Tools.deepGetJsonArrayVal(config, new String[]{"mobileapp", "headspin", "devices"});
        JsonArray devicesArrayByType = new JsonArray();
        for (JsonElement mobileDevice : mobileDevices) {
            if (mobileDevice.getAsJsonObject().get("platformName").getAsString().equalsIgnoreCase(deviceType.toLowerCase())) {
                devicesArrayByType.add(mobileDevice);
            }
        }
        return devicesArrayByType;
    }

    public JsonElement getBrowserLocalConfigByName(String deviceName) throws NullPointerException {
        JsonArray browsers = Tools.deepGetJsonArrayVal(config, new String[]{"webapp", "headspin", "browseres"});
        for (JsonElement browser : browsers) {
            if (browser.getAsJsonObject().get("deviceName").getAsString().equalsIgnoreCase(deviceName)) {
                return browser;
            }
        }
        throw new NullPointerException("Can not find out the browser: " + deviceName + " in local config.");
    }

    public String getDeviceUdidByLocalName(String deviceName) throws DeviceUnavailableException, IOException, InterruptedException {
        return getDeviceUdidByLocalName(deviceName, false);
    }

    public String getDeviceUdidByLocalName(String deviceName, boolean ignoreUnavailable) throws NullPointerException, IOException
            , InterruptedException, DeviceUnavailableException {
        String udid = null;
        long start = System.currentTimeMillis(); // current ms
        long end = start + (long) waitDeviceAvailableTimeout * 1000; // end ms
        if (deviceName.equalsIgnoreCase("ios") || deviceName.equalsIgnoreCase("android")) {
            boolean isDeviceFound = false;
            // find one device which satisfy the model is ios or android
            JsonArray devices = getLocalDevicesArrayByType(deviceName);
            while (!isDeviceFound && System.currentTimeMillis() <= end) {
                for (JsonElement device : devices) {
                    udid = device.getAsJsonObject().get("udid").getAsString();
                    if (isDeviceAvailable(udid)) {
                        isDeviceFound = true;
                        break;
                    }
                }
                if (!isDeviceFound) {
                    Logger.info("All " + deviceName + " device is unavailable now, wait " + polling + " seconds!");
                    Thread.sleep((long) polling * 1000);
                }
            }
        } else {
            udid = getDeviceLocalConfigByName(deviceName).getAsJsonObject().get("udid").getAsString();
            if (ignoreUnavailable) {
                return udid;
            }

            //Wait for available logic
            while (!isDeviceAvailable(udid) && System.currentTimeMillis() <= end) {
                Logger.info("The device " + udid + " is unavailable now, wait " + polling + " seconds!");
                Thread.sleep((long) polling * 1000);
            }
            if (!isDeviceAvailable(udid)) {
                throw new DeviceUnavailableException("After waiting "
                        + waitDeviceAvailableTimeout + " seconds, the device " + udid + " is still unavailable!");
            }
        }
        return udid;
    }

    public String getBrowserUdidByLocalName(String browserName) throws NullPointerException {
        return getBrowserLocalConfigByName(browserName).getAsJsonObject().get("udid").getAsString();
    }

    public String getAppIdByBuildCommit(String buildCommit) throws IOException {
        Map<String, Object> ret = listUploadedApp();
        JsonObject jsonObject = new JsonParser().parse((String) ret.get("body")).getAsJsonObject();
        JsonArray apps = jsonObject.getAsJsonArray("apps");
        String appId = null;
        for (JsonElement app : apps) {
            if (buildCommit.equalsIgnoreCase(app.getAsJsonObject().get("build_commit").getAsString())) {
                appId = app.getAsJsonObject().get("app_id").getAsString();
                break;
            }
        }
        return appId;
    }

    public Map<String, Object> installPreUploadApp(String deviceId, String appId, boolean sign) throws IOException {
        String url = baseURL + "/v1/app/" + appId + "/install/" + deviceId;
        HashMap<String, String> params = new HashMap<>();
        params.put("sign", "" + sign);
        return Post(url, header, params, null);
    }

    public Map<String, Object> uninstallApp(String deviceId, String app_identifier, boolean isAndroid) throws IOException {
        if (isAndroid) {
            return uninstallAndroidApp(deviceId, app_identifier);
        } else {
            return uninstallIOSApp(deviceId, app_identifier);
        }
    }

    private Map<String, Object> uninstallAndroidApp(String deviceId, String packageName) throws IOException {
        String url = baseURL + "/v0/adb/" + deviceId + "/uninstall";
        HashMap<String, String> params = new HashMap<>();
        params.put("package", "" + packageName);
        return Post(url, header, params, null);
    }

    private Map<String, Object> uninstallIOSApp(String deviceId, String bundleId) throws IOException {
        String url = baseURL + "/v0/idevice/" + deviceId + "/installer/uninstall";
        HashMap<String, String> params = new HashMap<>();
        params.put("appid", "" + bundleId);
        return Post(url, header, params, null);
    }

    public Map<String, Object> uploadAppToHeadspinRepo(String fileFullPath) throws IOException {
        String url = baseURL + "/v1/app/upload";
        header.put("Content-Type", "application/file");
        File file = new File(fileFullPath);
        return Post(url, header, file);
    }

    public boolean isDeviceAvailable(String udid) throws IOException {
        //1. 检查设备ID是否存在
        Map<String, Object> ret = getDeviceUrlsList();
        JsonObject jsonObject = new JsonParser().parse((String) ret.get("body")).getAsJsonObject();
        JsonArray devices = jsonObject.getAsJsonArray("devices");
        List<String> device_ids = new ArrayList<>();
        for (JsonElement device : devices) {
            String device_id = device.getAsJsonObject().get("device_id").getAsString();
            device_ids.add(device_id);
        }
        if (!device_ids.contains(udid)) {
            Logger.info("The device " + udid + " does not exist or unavailable!");
            return false;
        }

        //2. 检查设备是否被占用，当设备被占用是owner_email字段不为空
        ret = getDeviceList();
        jsonObject = new JsonParser().parse((String) ret.get("body")).getAsJsonObject();
        devices = jsonObject.getAsJsonArray("devices");
        List<String> used_device_ids = new ArrayList<>();
        for (JsonElement device : devices) {
            if (!device.getAsJsonObject().get("owner_email").isJsonNull()) {
                String device_id = device.getAsJsonObject().get("device_id").getAsString();
                used_device_ids.add(device_id);
            }
        }

        boolean result = !used_device_ids.contains(udid);
        if (!result) {
            Logger.info("The device " + udid + " is used by someone now!");
        }
        return result;
    }

    public Map<String, Object> getDeviceList() throws IOException {
        String url = baseURL + "/v0/devices";
        return Get(url, header, null);
    }

    public Map<String, Object> getDeviceUrlsList() throws IOException {
        String url = baseURL + "/v0/device-url";
        return Get(url, header, null);
    }

    private String makeUsableDriverUrl(String unusableUrl) {
        return unusableUrl.replace("{api_token}", token);
    }

    public String getDriverUrlByUdid(String udid) throws Exception {
        Logger.info("获取设备" + udid + "的driver url");
        JsonElement deviceInfo = getDeviceInfoByUdid(udid);
        return makeUsableDriverUrl(deviceInfo.getAsJsonObject().get("driver_url").getAsString());
    }

    public Map<String, Object> getAutomationConfig() throws IOException {
        String url = baseURL + "/v0/devices/automation-config";
        return Get(url, header, null);
    }

    public JsonElement getDeviceInfoByUdid(String udid) throws Exception {
        Map<String, Object> ret = getAutomationConfig();
        JsonObject automationConfigs = new JsonParser().parse((String) ret.get("body")).getAsJsonObject();
        Set<Map.Entry<String, JsonElement>> configs = automationConfigs.entrySet();
        for (Map.Entry<String, JsonElement> config : configs) {
            String key = config.getKey();
            JsonElement value = config.getValue();
            if (key.split("@")[0].equalsIgnoreCase(udid)) {
                return value;
            }
        }
        throw new Exception("The device with udid " + udid + " could not be found!");
    }

    public JsonElement getDeviceCapabilitiesByUdid(String udid) throws Exception {
        return getDeviceInfoByUdid(udid).getAsJsonObject().get("capabilities");
    }

    public JsonElement getHeadSpinDeviceCapByName(String DeviceName) {
        //1. 如果deviceName == ios or android, 则按顺序获取一个available device
        //2. 如果deviceName == 具体设备名称，则获取该设备udid 然后返回设备的desire_cap
        return null;
    }

    public Map<String, Object> lockDevice(String deviceId, int idleTime) throws IOException {
        String url = baseURL + "/v0/devices/lock";
        HashMap<String, String> params = new HashMap<>();
        params.put("idleTimeout", "" + idleTime);
        String json = "{\"device_id\": \"" + deviceId + "\"}";
        return Post(url, header, params, json);
    }

    public Map<String, Object> unlockDevice(String deviceId) throws IOException {
        String url = baseURL + "/v0/devices/unlock";
        String json = "{\"device_id\": \"" + deviceId + "\"}";
        return Post(url, header, null, json);
    }

    public Map<String, Object> delPreUploadApp(String appId) throws IOException {
        String url = baseURL + "/v1/app/" + appId + "/delete";
        return Delete(url, header, null);
    }

    public Map<String, Object> listUploadedApp() throws IOException {
        String url = baseURL + "/v1/apps";
        return Get(url, header, null);
    }

    /**
     * 删除之前上传到Headspin Repo上的app
     *
     * @param appIdentifier: app identifier, iOS: Bundle Name, Android: Package Name.
     * @return true: 删除成功
     * false: 清除失败
     */
    public boolean clearUploadedAppByIdentifier(String appIdentifier) throws IOException {
        Map<String, Object> ret = listUploadedApp();
        JsonObject jsonObject = new JsonParser().parse((String) ret.get("body")).getAsJsonObject();
        JsonArray apps = jsonObject.getAsJsonArray("apps");
        if (apps != null) {
            for (JsonElement app : apps) {
                if (appIdentifier.equalsIgnoreCase(app.getAsJsonObject().get("app_identifier").getAsString())) {
                    String appId = app.getAsJsonObject().get("app_id").getAsString();
                    Map<String, Object> delRet = delPreUploadApp(appId);
                    if (!delRet.get("code").equals(200)) {
                        Logger.error("Delete package " + appIdentifier + " failed from the headspin repo!");
                        return false;
                    }
                }
            }
        }
        return true;
    }
}
